/**
 * Copyright 2019 吉鼎科技.
 *
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.easyplatform.support.scripting;

import cn.easyplatform.EasyPlatformWithLabelKeyException;
import cn.easyplatform.ScriptEvalException;
import cn.easyplatform.ScriptEvalExitException;
import cn.easyplatform.ScriptRuntimeException;
import cn.easyplatform.contexts.ListContext;
import cn.easyplatform.contexts.RecordContext;
import cn.easyplatform.contexts.ReportContext;
import cn.easyplatform.contexts.WorkflowContext;
import cn.easyplatform.interceptor.CommandContext;
import cn.easyplatform.lang.Mirror;
import cn.easyplatform.support.scripting.cmd.ListCmd;
import cn.easyplatform.support.scripting.cmd.MainCmd;
import cn.easyplatform.support.scripting.cmd.ReportCmd;
import cn.easyplatform.support.scripting.parser.SimpleScriptParser;
import org.apache.commons.lang3.StringUtils;
import org.mozilla.javascript.EcmaError;
import org.mozilla.javascript.JavaScriptException;
import org.mozilla.javascript.RhinoException;
import org.mozilla.javascript.WrappedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Map.Entry;

/**
 * @author <a href="mailto:davidchen@epclouds.com">littleDog</a> <br/>
 * @since 2.0.0 <br/>
 */
public final class ScriptUtils {

    private ScriptUtils() {
    }

    private final static Logger log = LoggerFactory.getLogger(ScriptUtils.class);

    public final static String parse(CommandContext cc, RecordContext rc, String script) {
        return new SimpleScriptParser().parse(cc, rc, script);
    }

    public final static RuntimeException handleScriptException(
            CommandContext cc, RhinoException ex) {
        if (ex instanceof JavaScriptException) {
            Object val = ((JavaScriptException) ex).getValue();
            if ("NativeError".equals(val.getClass().getSimpleName()))
                ex = (RhinoException) Mirror.me(val).getEjecting("stackProvider").eject(val);
        }
        int line = ex.lineNumber();
        if (ex instanceof EcmaError) {
            EcmaError error = (EcmaError) ex;
            line = error.lineNumber();
        } /*
         * else if (!(ex instanceof WrappedException)) { for (ScriptStackElement
         * e : ex.getScriptStack()) { if (e.functionName == null) { line =
         * e.lineNumber; break; } } }
         */
        if (ex instanceof WrappedException) {
            Throwable we = ((WrappedException) ex).getWrappedException();
            if (we instanceof ScriptEvalExitException) {
                ScriptEvalExitException e = (ScriptEvalExitException) we;
                e.setLine(line);
                return e;
            } else if (we instanceof EasyPlatformWithLabelKeyException) {
                EasyPlatformWithLabelKeyException ge = (EasyPlatformWithLabelKeyException) we;
                if (ge.getLineNo() <= 0)
                    ge.setLineNo(line);
                return (EasyPlatformWithLabelKeyException) we;
            } else if (we instanceof ScriptRuntimeException) {
                return (ScriptRuntimeException) we;
            }
        }
        if (log.isErrorEnabled())
            log.error("eval", ex);
        String msg = null;
        if (ex instanceof WrappedException) {
            Throwable t = ((WrappedException) ex).getWrappedException();
            for (StackTraceElement ste : t.getStackTrace()) {
                if (ste.getClassName().startsWith("cn.easyplatform")) {
                    msg = String.format("Row:%s: %s.%s", ste.getLineNumber(), StringUtils.substringAfterLast(ste.getClassName(), "."), ste.getMethodName());
                    break;
                }
            }
            if (msg == null)
                msg = t.getMessage();
        } else
            msg = ex.getMessage();
        if (cc.getUser().isDebugEnabled())
            cc.send(msg);
        return new ScriptEvalException(msg, line, ex.columnNumber());
    }

    public final static MainCmd createVariables(
            RhinoScriptable scope, CommandContext cc, WorkflowContext ctx,
            RecordContext... data) {
        MainCmd cmd = new MainCmd(scope, cc, ctx);
        cmd.setData(data);
        scope.setVariable("$", cmd);
        if (ctx != null) {
            // 列表
            createListVariables(cmd, scope, "", ctx);
            // 报表
            for (ReportContext rc : ctx.getReports())
                scope.setVariable(rc.getId(), new ReportCmd(cmd, rc));
            // 子功能
            for (Entry<String, String> entry : ctx.getChildren()) {
                WorkflowContext embbed = cc.getWorkflowContext(entry.getValue());
                if (embbed == null)
                    throw new EasyPlatformWithLabelKeyException(
                            "context.not.found", entry.getKey());
                createEmbbedTaskVariables(cc, scope, entry.getKey(), embbed);
            }
            // 父功能
            WorkflowContext parent = null;
            if (ctx.getParentId() != null) {
                parent = cc.getWorkflowContext(ctx.getParentId());
                if (parent == null)
                    throw new EasyPlatformWithLabelKeyException(
                            "context.parent.not.found", ctx.getParentId());
            } else if (ctx.getRecord().getParameter("850") != null) {// 列表open
                String hostId = ctx.getParameterAsString("800");
                parent = cc.getWorkflowContext(hostId);
                if (parent == null)
                    throw new EasyPlatformWithLabelKeyException(
                            "context.parent.not.found", hostId);
            }
            if (parent != null)
                createParentVariables(cc, scope, "parent", parent);
            else
                scope.setVariable("parent", null);
        }
        return cmd;
    }

    private final static void createEmbbedTaskVariables(CommandContext cc,
                                                        RhinoScriptable scope, String prefix, WorkflowContext ctx) {
        MainCmd cmd = new MainCmd(scope, cc,
                cc.getWorkflowContext());
        cmd.setData(ctx.getRecord());
        scope.setVariable(prefix, cmd);
        createListVariables(cmd, scope, prefix, ctx);
    }

    private final static void createListVariables(MainCmd cmd, RhinoScriptable scope, String prefix,
                                                  WorkflowContext ctx) {
        if (!ctx.getLists().isEmpty()) {
            String px = prefix.equals("") ? "" : prefix + "$";
            for (ListContext lc : ctx.getLists())
                scope.setVariable(px + lc.getId(), new ListCmd(ctx, lc,
                        cmd));
        }
    }

    private final static void createParentVariables(CommandContext cc,
                                                    RhinoScriptable scope, String prefix, WorkflowContext ctx) {
        MainCmd cmd = new MainCmd(scope, cc, ctx);
        cmd.setData(ctx.getRecord());
        scope.setVariable(prefix, cmd);
        createListVariables(cmd, scope, prefix, ctx);
        for (Entry<String, String> entry : ctx.getChildren()) {
            WorkflowContext embbed = cc.getWorkflowContext(entry.getValue());
            if (embbed == null)
                throw new EasyPlatformWithLabelKeyException(
                        "context.not.found", entry.getKey());
            createEmbbedTaskVariables(cc, scope, prefix + "$" + entry.getKey(),
                    embbed);
        }
        if (ctx.getParentId() != null) {
            WorkflowContext parent = cc.getWorkflowContext(ctx.getParentId());
            if (parent != null)
                createParentVariables(cc, scope, prefix + "$parent", parent);
        }
    }
}
